home *** CD-ROM | disk | FTP | other *** search
/ MacTech 1 to 12 / MacTech-vol-1-12.toast / Source / WASTE 1.2 Distribution / WASTE 1.2 / WESelecting.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-06-25  |  41.4 KB  |  1,653 lines  |  [TEXT/CWIE]

  1. /*
  2.  *    WESelecting.c
  3.  *
  4.  *    WASTE PROJECT
  5.  *  Drawing Selections, Activating, Updating, Scrolling, etc.
  6.  *
  7.  *  Copyright (c) 1993-1996 Marco Piovanelli
  8.  *    All Rights Reserved
  9.  *
  10.  *  C port by Dan Crevier
  11.  *
  12.  */
  13.  
  14.  
  15. #include "WASTEIntf.h"
  16.  
  17. // values for _WEArrowOffset action parameter:
  18.  
  19. enum {
  20.  
  21. // plain arrow keys
  22.     kGoLeft            =    0,
  23.     kGoRight        =    1,
  24.     kGoUp            =    2,
  25.     kGoDown            =    3,
  26.  
  27. // modifiers
  28.     kOption            =    4,
  29.     kCommand        =    8,
  30.  
  31. // option + arrow combos
  32.     kGoWordStart    =    kGoLeft + kOption,
  33.     kGoWordEnd        =    kGoRight + kOption,
  34.     kGoTextStart    =    kGoUp + kOption,
  35.     kGoTextEnd        =    kGoDown + kOption,
  36.  
  37. // command + arrow combos
  38.     kGoLineStart    =    kGoLeft + kCommand,
  39.     kGoLineEnd        =    kGoRight + kCommand,
  40.     kGoPageStart    =    kGoUp + kCommand,
  41.     kGoPageEnd        =    kGoDown + kCommand
  42. };
  43.  
  44. INLINE pascal void _WEClearHiliteBit(void)
  45. {
  46.     LMSetHiliteMode(LMGetHiliteMode() & 0x7F);
  47. }
  48.  
  49. static Boolean SLPixelToChar(LinePtr pLine, const WERunAttributes *pAttrs,
  50.         Ptr pSegment, SInt32 segmentStart, SInt32 segmentLength,
  51.         JustStyleCode styleRunPosition, WEHandle hWE, void *callbackData)
  52. {
  53.     struct SLPixelToCharData *cd = (struct SLPixelToCharData *) callbackData;
  54.     WEPtr pWE = *hWE;
  55.     Fixed slop;
  56.     SInt16 cType;
  57.     Fixed oldWidth;
  58. #if WASTE_OBJECTS
  59.     Fixed objectWidth;
  60.     Fixed subWidth;
  61. #endif
  62.  
  63.     // if this is the first style run on the line, subtract pen indent from pixelWidth
  64.     if (IS_FIRST_RUN(styleRunPosition))
  65.     {
  66.         cd->pixelWidth -= BSL(_WECalcPenIndent(pLine->lineSlop, pWE->alignment), 16);
  67.     }
  68.  
  69.     // if pixelWidth is gone negative already, the point is on the trailing edge of first glyph
  70.     if (cd->pixelWidth < 0)
  71.     {
  72.         cd->offset = segmentStart;
  73.         cd->edge = kTrailingEdge;
  74.         return true;    // stop looping
  75.     }
  76.  
  77.     oldWidth = cd->pixelWidth;
  78.  
  79. #if WASTE_OBJECTS
  80.     if (pAttrs->runStyle.tsObject != nil)
  81.     {
  82.  
  83.         // EMBEDDED OBJECT
  84.         // calculate object width as Fixed
  85.         objectWidth = BSL((*pAttrs->runStyle.tsObject)->objectSize.h, 16);
  86.  
  87.         // subtract object width from pixelWidth
  88.         cd->pixelWidth -= objectWidth;
  89.  
  90. #if WASTE_OBJECTS_ARE_GLYPHS
  91.  
  92.         // find out whether the point is in the leftmost half of the object,
  93.         // in the rightmost half or past the object
  94.         subWidth = objectWidth >> 1;    // divide by two
  95.         if (cd->pixelWidth + subWidth < 0)
  96.         {
  97.             cd->offset = segmentStart;
  98.             cd->edge = kLeadingEdge;        // point is in leftmost half of object
  99.         }
  100.         else
  101.         {
  102.             cd->offset= segmentStart + 1;
  103.             if (cd->pixelWidth < 0)
  104.                 cd->edge = kTrailingEdge;    // point is in rightmost half of object
  105.             else
  106.                 cd->edge = kLeadingEdge;    // point is past object
  107.         }
  108. #else
  109.  
  110.         // find out whether the point is in the leftmost quarter of the object,
  111.         // in the middle half, in the rightmost quarter or past the object
  112.         subWidth = objectWidth >> 2;    // divide by four
  113.         if (cd->pixelWidth + subWidth < 0)
  114.         {
  115.             cd->offset = segmentStart;
  116.             if (cd->pixelWidth + objectWidth < subWidth)
  117.                 cd->edge = kLeadingEdge;        // point is in leftmost quarter of object
  118.             else
  119.                 cd->edge = kObjectEdge;        // point is in middle half of object
  120.         }
  121.         else
  122.         {
  123.             cd->offset = segmentStart + 1;
  124.             if (cd->pixelWidth < 0)
  125.                 cd->edge = kTrailingEdge;    // point is in rightmost quarter of object
  126.             else
  127.                 cd->edge = kLeadingEdge;        // point is past object
  128.         }
  129. #endif // WASTE_OBJECTS_ARE_GLYPHS
  130.     }
  131.     else
  132. #endif // WASTE_OBJECTS
  133.     {
  134.  
  135.         // REGULAR TEXT
  136.  
  137.         // if this is the last segment on the line, strip the last blank character (if any),
  138.         // unless it is the last non-CR character in the whole text stream
  139.         if (IS_LAST_RUN(styleRunPosition))
  140.         {
  141.             if ((segmentStart + segmentLength < pWE->textLength) ||
  142.                 pSegment[segmentLength - 1] == kEOL)
  143.             {
  144.                 cType = CallWECharTypeProc(pSegment, segmentLength - 1, FontScript(),
  145.                      hWE, pWE->charTypeHook);
  146.                 if ((cType & (smcTypeMask + smcClassMask)) == smCharPunct + smPunctBlank)
  147.                 {
  148.                     segmentLength -= ((cType & smcDoubleMask) ? 2 : 1);
  149.                 }
  150.             }
  151.         }
  152.  
  153.         // calculate slop for this text segment (justified text only)
  154.         if (pWE->alignment == weJustify)
  155.         {
  156.             slop = FixMul(PortionLine(pSegment, segmentLength, styleRunPosition,
  157.                     kOneToOneScaling, kOneToOneScaling), pLine->lineJustAmount);
  158.         }
  159.         else
  160.         {
  161.             slop = 0;
  162.         }
  163.  
  164.         // call PixelToChar hook for this segment
  165.         cd->offset = segmentStart + CallWEPixelToCharProc(pSegment, segmentLength,
  166.                 slop, &cd->pixelWidth, &cd->edge, styleRunPosition, cd->hPos,
  167.                 hWE, pWE->pixelToCharHook);
  168.     }
  169.  
  170.     // increment hPos by change in pixelWidth
  171.     cd->hPos += (oldWidth - cd->pixelWidth);
  172.  
  173.     // if pixelWidth has gone negative, we're finished; otherwise go to next run
  174.     return (cd->pixelWidth < 0);
  175. }
  176.  
  177. pascal SInt32 WEGetOffset(const LongPt *thePoint, WEEdge *edge, WEHandle hWE)
  178. {
  179.     // given a long point in local coordinates,
  180.     // find the text offset corresponding to the nearest glyph
  181.  
  182.     WEPtr pWE;
  183.     SInt32 lineIndex;
  184.     LongPt tempPoint;
  185.     struct SLPixelToCharData cd;
  186.     Boolean saveWELock;
  187.  
  188.     // lock the WE record
  189.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  190.     pWE = *hWE;
  191.  
  192.     // tempPoint is thePoint, relative to the top left corner of the dest rect
  193.     tempPoint.v = thePoint->v - pWE->destRect.top;
  194.     tempPoint.h = thePoint->h - pWE->destRect.left;
  195.  
  196.     // if the point is above the destination rect, return zero
  197.     if (tempPoint.v < 0)
  198.     {
  199.         cd.offset = 0;
  200.         cd.edge = kTrailingEdge;
  201.     }
  202.     else
  203.     {
  204.         // if the point is below the last line, return last char offset
  205.         if (tempPoint.v >= WEGetHeight(0, LONG_MAX, hWE))
  206.         {
  207.             cd.offset = pWE->textLength;
  208.             cd.edge = kLeadingEdge;
  209.         }
  210.         else
  211.         {
  212.             // find the line index corresponding to the vertical pixel offset
  213.             lineIndex = _WEPixelToLine(tempPoint.v, hWE);
  214.  
  215.             // express the horizontal pixel offset as a Fixed value
  216.             cd.pixelWidth = BSL(tempPoint.h, 16);
  217.  
  218.             // walk through the segments on this line calling PixelToChar
  219.             cd.hPos = 0;
  220.             cd.offset = 0;
  221.             _WESegmentLoop(lineIndex, lineIndex, SLPixelToChar, &cd, hWE);
  222.         }
  223.     }
  224.  
  225.     // unlock the WE record
  226.     _WESetHandleLock((Handle) hWE, saveWELock);
  227.  
  228.     // return offset/edge pair
  229.     if (edge != nil)
  230.     {
  231.         *edge = cd.edge;
  232.     }
  233.     return cd.offset;
  234. }
  235.  
  236. static Boolean SLCharToPixel(LinePtr pLine, const WERunAttributes *pAttrs,
  237.         Ptr pSegment, SInt32 segmentStart, SInt32 segmentLength,
  238.         JustStyleCode styleRunPosition, WEHandle hWE, void *callbackData)
  239. {
  240.     struct SLCharToPixelData *cd = (struct SLCharToPixelData *) callbackData;
  241.     WEPtr pWE = *hWE;
  242.     Fixed slop;
  243.     SInt16 width;
  244.     Boolean isInSegment;
  245.  
  246.     // is offset within this segment?
  247.     isInSegment = (cd->offset < segmentStart + segmentLength);
  248.  
  249.     // if this is the first style run on the line, add pen indent to thePoint.h
  250.     if (IS_FIRST_RUN(styleRunPosition))
  251.     {
  252.         cd->thePoint->h += _WECalcPenIndent(pLine->lineSlop, pWE->alignment);
  253.     }
  254.  
  255. #if WASTE_OBJECTS
  256.     if (pAttrs->runStyle.tsObject != nil)
  257.     {
  258.  
  259.         // EMBEDDED OBJECT
  260.         width = isInSegment ? 0 : (*pAttrs->runStyle.tsObject)->objectSize.h;
  261.     }
  262.     else
  263. #endif
  264.     {
  265.         // REGULAR TEXT
  266.         slop = 0;
  267.  
  268.         // calculate slop for this text segment (justified text only)
  269.         if (pWE->alignment == weJustify)
  270.         {
  271.             slop = FixMul(PortionLine(pSegment, segmentLength, styleRunPosition,
  272.                 kOneToOneScaling, kOneToOneScaling), pLine->lineJustAmount);
  273.         }
  274.  
  275.         // call CharToPixel to get width of segment up to specified offset
  276.         width = CallWECharToPixelProc(pSegment, segmentLength,
  277.             slop, cd->offset - segmentStart, cd->direction, styleRunPosition, cd->thePoint->h,
  278.             hWE, pWE->charToPixelHook);
  279.     }
  280.  
  281.     // advance thePoint.h by the width of this segment
  282.     cd->thePoint->h += width;
  283.  
  284.     // drop out of loop when we reach offset
  285.     return isInSegment;
  286. }
  287.  
  288. pascal void WEGetPoint(SInt32 offset, SInt16 direction, LongPt *thePoint, SInt16 *lineHeight, WEHandle hWE)
  289. {
  290.     // given a byte offset into the text, find the corresponding glyph position
  291.     // this routine is useful for highlighting the text and for positioning the caret
  292.  
  293.     WEPtr pWE;
  294.     LineRec *pLine;
  295.     SInt32 lineIndex;
  296.     SInt32 height;
  297.     struct SLCharToPixelData cd;
  298.     Boolean saveWELock;
  299.  
  300.     // lock the WE record
  301.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  302.     pWE = *hWE;
  303.  
  304.     // the base point is the top left corner of the destination rectangle
  305.     *thePoint = * (LongPt *) &pWE->destRect;
  306.  
  307.     // first of all find the line on which the glyph lies
  308.     lineIndex = WEOffsetToLine(offset, hWE);
  309.  
  310.     // calculate the vertical coordinate and the line height
  311.     pLine = *pWE->hLines + lineIndex;
  312.     thePoint->v += pLine->lineOrigin;
  313.     height = pLine[1].lineOrigin - pLine[0].lineOrigin;
  314.  
  315.     if ((offset == pWE->textLength) && (WEGetChar(offset - 1, hWE) == kEOL))
  316.     {
  317.         // SPECIAL CASE: if offset is past the last character and
  318.         // the last character is a carriage return, return a point below the last line
  319.  
  320.         thePoint->v += height;
  321.         thePoint->h += _WECalcPenIndent(pWE->destRect.right - pWE->destRect.left, pWE->alignment);
  322.     }
  323.     else
  324.     {
  325.         cd.offset = offset;
  326.         cd.thePoint = thePoint;
  327.         cd.direction = direction;
  328.  
  329.         // to get the horizontal coordinate, walk through the style runs on this line
  330.         _WESegmentLoop(lineIndex, lineIndex, SLCharToPixel, &cd, hWE);
  331.     }
  332.  
  333.     // pin the horizontal coordinate to the destination rectangle
  334.     thePoint->h = _WEPinInRange(thePoint->h, pWE->destRect.left, pWE->destRect.right);
  335.  
  336.     // unlock the WE record
  337.     _WESetHandleLock((Handle) hWE, saveWELock);
  338.     
  339.     // copy line height
  340.     if (lineHeight != nil)
  341.     {
  342.         *lineHeight = height;
  343.     }
  344. }
  345.  
  346. pascal void WEFindLine(SInt32 offset, WEEdge edge, SInt32 *lineStart, SInt32 *lineEnd, WEHandle hWE)
  347. {
  348. #pragma unused(edge)
  349.     WEPtr pWE = *hWE;
  350.     LineRec *pLine;
  351.  
  352.     pLine = *pWE->hLines + WEOffsetToLine(offset, hWE);
  353.     *lineStart = pLine[0].lineStart;
  354.     *lineEnd = pLine[1].lineStart;
  355. }
  356.  
  357. pascal ScriptCode _WEGetContext(SInt32 offset, SInt32 *contextStart, SInt32 *contextEnd,
  358.                         WEHandle hWE)
  359. {
  360.     // This function finds a range of characters ("context"), all belonging to the same script
  361.     // and centered around the specified offset.
  362.     // The function result is the ID of a font belonging to this script.
  363.     // Ideally, the context should consist of a whole script run, but in practice the returned
  364.     // context can be narrower, for performance and other reasons (see below)
  365.  
  366.     SInt32 index, saveIndex, saveRunEnd;
  367.     WERunInfo runInfo;
  368.     ScriptCode script1, script2;
  369.     SInt16 retval;
  370.  
  371.     if (BTST((*hWE)->flags, weFNonRoman))
  372.     {
  373.         // if more than one script is installed, limit the search of script run boundaries
  374.         // to a single line, for speed's sake
  375.         WEFindLine(offset, kLeadingEdge, contextStart, contextEnd, hWE);
  376.  
  377.         // find the style run the specified offset is in
  378.         index = _WEOffsetToRun(offset, hWE);
  379.         _WEGetIndStyle(index, &runInfo, hWE);
  380.  
  381.         // find the script code associated with this style run
  382.         script1 = FontToScript(runInfo.runAttrs.runStyle.tsFont);
  383.  
  384.         // the script code is returned as function result
  385.         retval = script1;
  386.  
  387.         // save index and runInfo.runEnd for the second while loop
  388.         saveIndex = index;
  389.         saveRunEnd = runInfo.runEnd;
  390.  
  391.         // walk backwards across style runs preceding offset, looking for a script run boundary
  392.         while (runInfo.runStart > *contextStart)
  393.         {
  394.             index--;
  395.             _WEGetIndStyle(index, &runInfo, hWE);
  396.             script2 = FontToScript(runInfo.runAttrs.runStyle.tsFont);
  397.             if (script1 != script2)
  398.             {
  399.                 *contextStart = runInfo.runEnd;
  400.                 break;
  401.             }
  402.         }
  403.  
  404.         // restore index and runInfo.runEnd
  405.         index = saveIndex;
  406.         runInfo.runEnd = saveRunEnd;
  407.  
  408.         // walk forward across style runs following offset, looking for a script run boundary
  409.         while (runInfo.runEnd < *contextEnd)
  410.         {
  411.             index++;
  412.             _WEGetIndStyle(index, &runInfo, hWE);
  413.             script2 = FontToScript(runInfo.runAttrs.runStyle.tsFont);
  414.             if (script1 != script2)
  415.             {
  416.                 *contextEnd = runInfo.runStart;
  417.                 break;
  418.             }
  419.         }
  420.     }
  421.     else
  422.     {
  423.         // only the Roman script is enabled: the whole text constitutes one script run
  424.         retval = smRoman;
  425.         *contextStart = 0;
  426.         *contextEnd = (*hWE)->textLength;
  427.     }
  428.  
  429.     // make sure the range identified by contextStart/contextEnd is contained within
  430.     // the 32K byte range centered around the specified offset
  431.     // the reason for this is that many Script Manager routines (e.g. FindWord and CharByte)
  432.     // only accept 16-bit offsets, rather than 32-bit offsets
  433.  
  434.     *contextStart = _WEPinInRange(*contextStart, offset - (SHRT_MAX / 2), offset);
  435.     *contextEnd = _WEPinInRange(*contextEnd, offset, offset + (SHRT_MAX / 2));
  436.  
  437.     return retval;
  438. }
  439.  
  440. pascal ScriptCode _WEGetRestrictedContext(SInt32 offset, SInt32 *contextStart, SInt32 *contextEnd,
  441.                         WEHandle hWE)
  442. {
  443.     // This function finds a range of characters ("context"), all belonging to the same script
  444.     // and centered around the specified offset.
  445.     // This function returns a script run subrange and is more efficient than
  446.     // _WEGetContext because it doesn't try to find the script boundaries accurately.
  447.  
  448.     WERunInfo runInfo;
  449.  
  450.     // just find the style run the specified offset is in
  451.     WEGetRunInfo(offset, &runInfo, hWE);
  452.     *contextStart = runInfo.runStart;
  453.     *contextEnd = runInfo.runEnd;
  454.  
  455.     // make sure the range identified by contextStart/contextEnd is contained within
  456.     // the 32K byte range centered around the specified offset
  457.     // the reason for this is that many Script Manager routines (e.g. FindWord and CharByte)
  458.     // only accept 16-bit offsets, rather than 32-bit offsets
  459.  
  460.     *contextStart = _WEPinInRange(*contextStart, offset - (SHRT_MAX / 2), offset);
  461.     *contextEnd = _WEPinInRange(*contextEnd, offset, offset + (SHRT_MAX / 2));
  462.  
  463.     return FontToScript(runInfo.runAttrs.runStyle.tsFont);
  464. }
  465.  
  466. pascal void WEFindWord(SInt32 offset, WEEdge edge, SInt32 *wordStart, SInt32 *wordEnd, WEHandle hWE)
  467. {
  468.     WEPtr pWE;
  469.     ScriptCode script;
  470.     SInt32 contextStart, contextEnd;
  471.     Handle hText;
  472.     OffsetTable wordBreaks;
  473.     Boolean saveTextLock, saveWELock;
  474.  
  475.     // lock the WE record
  476.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  477.     pWE = *hWE;
  478.  
  479.     // find a script context containing the specified offset
  480.     // (words cannot straddle script boundaries)
  481.     script = _WEGetContext(offset, &contextStart, &contextEnd, hWE);
  482.  
  483.     // lock the text
  484.     hText = pWE->hText;
  485.     saveTextLock = _WESetHandleLock(hText, true);
  486.  
  487.     // call the word break hook
  488.     CallWEWordBreakProc(*hText + contextStart, contextEnd - contextStart,
  489.         offset - contextStart, edge, wordBreaks, script, hWE, pWE->wordBreakHook);
  490.  
  491.     // unlock the text
  492.     _WESetHandleLock(hText, saveTextLock);
  493.  
  494.     // calculate wordStart and wordEnd relative to the beginning of the text
  495.     *wordStart = contextStart + wordBreaks[0].offFirst;
  496.     *wordEnd = contextStart + wordBreaks[0].offSecond;
  497. }
  498.  
  499. pascal SInt16 WECharByte(SInt32 offset, WEHandle hWE)
  500. {
  501.     WEPtr pWE;
  502.     ScriptCode script;
  503.     SInt32 contextStart, contextEnd;
  504.     SInt16 retval;
  505.     Boolean saveWELock, saveTextLock;
  506.     Handle hText;
  507.  
  508.     retval = smSingleByte;
  509.     pWE = *hWE;
  510.  
  511.     // lock the WE record
  512.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  513.     pWE = *hWE;
  514.  
  515.     // do nothing unless there is at least one double-byte script system installed
  516.     // and make sure offset is within allowed bounds
  517.     if (BTST(pWE->flags, weFDoubleByte))
  518.     {
  519.         if ((offset >= 0) && (offset < pWE->textLength))
  520.         {
  521.  
  522.             // find a script context containing the specified offset
  523.             script = _WEGetRestrictedContext(offset, &contextStart, &contextEnd, hWE);
  524.  
  525.             // lock the text
  526.             hText = pWE->hText;
  527.             saveTextLock = _WESetHandleLock(hText, true);
  528.  
  529.             // pass the CharByte hook a pointer to the beginning of the style run
  530.             retval = CallWECharByteProc(*hText + contextStart,
  531.                 offset - contextStart, script, hWE, pWE->charByteHook);
  532.  
  533.             // unlock the text
  534.             _WESetHandleLock(hText, saveTextLock);
  535.         }
  536.     }
  537.  
  538.     // unlock the WE record
  539.     _WESetHandleLock((Handle) hWE, saveWELock);
  540.  
  541.     return retval;
  542.  
  543. }
  544.  
  545. pascal SInt16 WECharType(SInt32 offset, WEHandle hWE)
  546. {
  547.     WEPtr pWE;
  548.     ScriptCode script;
  549.     SInt32 contextStart, contextEnd;
  550.     Handle hText;
  551.     Boolean saveWELock, saveTextLock;
  552.     SInt16 retval;
  553.  
  554.     retval = 0;
  555.  
  556.     // lock the WE record
  557.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  558.     pWE = *hWE;
  559.  
  560.     // make sure offset is within allowed bounds
  561.     if ((offset >= 0) && (offset < pWE->textLength))
  562.     {
  563.  
  564.         // find a script context containing the specified offset
  565.         script = _WEGetRestrictedContext(offset, &contextStart, &contextEnd, hWE);
  566.  
  567.         // lock the text
  568.         hText = pWE->hText;
  569.         saveTextLock = _WESetHandleLock(hText, true);
  570.  
  571.         // pass the CharType hook a pointer to the beginning of the style run
  572.         retval = CallWECharTypeProc(*hText + contextStart,
  573.             offset - contextStart, script, hWE, pWE->charTypeHook);
  574.  
  575.         // unlock the text
  576.         _WESetHandleLock(hText, saveTextLock);
  577.     }
  578.  
  579.     // unlock the WE record
  580.     _WESetHandleLock((Handle) hWE, saveWELock);
  581.  
  582.     return retval;
  583. }
  584.  
  585.  
  586. pascal void _WEDrawCaret(SInt32 offset, WEHandle hWE)
  587. {
  588.     WEPtr pWE = *hWE;    // assume WE record is already locked
  589.     LongPt thePoint;
  590.     Rect caretRect;
  591.     SInt16 caretHeight;
  592.     GrafPtr savePort;
  593.     RgnHandle saveClip;
  594.  
  595.     // find the caret position using WEGetPoint
  596.     WEGetPoint(offset, hilite, &thePoint, &caretHeight, hWE);
  597.     WELongPointToPoint(&thePoint, (Point *) &caretRect.top);
  598.     if (caretRect.left > pWE->destRect.left)
  599.     {
  600.         caretRect.left--;
  601.     }
  602.  
  603.     // calculate caret rectangle
  604.     caretRect.bottom = caretRect.top + caretHeight;
  605.     caretRect.right = caretRect.left + kCaretWidth;
  606.  
  607.     // set up the port
  608.     GetPort(&savePort);
  609.     SetPort(pWE->port);
  610.  
  611.     // clip to the view region
  612.     saveClip = NewRgn();
  613.     GetClip(saveClip);
  614.     SetClip(pWE->viewRgn);
  615.  
  616.     // draw the caret
  617.     InvertRect(&caretRect);
  618.  
  619.     // restore the clip region
  620.     SetClip(saveClip);
  621.     DisposeRgn(saveClip);
  622.  
  623.     // restore the port
  624.     SetPort(savePort);
  625. }
  626.  
  627. pascal void _WEBlinkCaret(WEHandle hWE)
  628. {
  629.     WEPtr pWE = *hWE;    // assume WE record is already locked
  630.  
  631.     // do nothing if we're not active
  632.     if (!BTST(pWE->flags, weFActive))
  633.         return;
  634.  
  635. #if WASTE_NO_RO_CARET
  636.     if (BTST(pWE->features, weFReadOnly) && !BTST(pWE->flags, weFCaretVisible))
  637.         return;
  638. #endif
  639.  
  640.     // redraw the caret, in XOR mode
  641.     _WEDrawCaret(pWE->selStart, hWE);
  642.  
  643.     // keep track of the current caret visibility status
  644.     BCHG(pWE->flags, weFCaretVisible);    // invert flag
  645.  
  646.     // update caretTime
  647.     pWE->caretTime = TickCount();
  648.  
  649. }
  650.  
  651. pascal RgnHandle WEGetHiliteRgn(SInt32 rangeStart, SInt32 rangeEnd, WEHandle hWE)
  652. {
  653.     // returns the hilite region corresponding to the specified range
  654.     // the caller is responsible for disposing of the returned region
  655.     // when it's finished with it
  656.  
  657.     WEPtr pWE;
  658.     RgnHandle hiliteRgn;
  659.     LongRect selRect;
  660.     LongPt firstPoint, lastPoint;
  661.     SInt16 firstLineHeight, lastLineHeight;
  662.     Rect r;
  663.     GrafPtr savePort;
  664.     Boolean saveWELock;
  665.  
  666.     // lock the WE record
  667.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  668.     pWE = *hWE;
  669.  
  670.     // set up the port
  671.     GetPort(&savePort);
  672.     SetPort(pWE->port);
  673.  
  674.     // make sure rangeStart comes before rangeEnd
  675.     _WEReorder(&rangeStart, &rangeEnd);
  676.  
  677.     // calculate pixel location corresponding to rangeStart
  678.     WEGetPoint(rangeStart, hilite, &firstPoint, &firstLineHeight, hWE);
  679.  
  680.     // calculate pixel location corresponding to rangeEnd
  681.     WEGetPoint(rangeEnd, hilite, &lastPoint, &lastLineHeight, hWE);
  682.  
  683.     // open a region: rects to be hilited will be accumulated in this
  684.     OpenRgn();
  685.  
  686.     if (firstPoint.v == lastPoint.v)
  687.     {
  688.         // selection range encompasses only one line
  689.         WESetLongRect(&selRect, firstPoint.h, firstPoint.v, lastPoint.h, lastPoint.v + lastLineHeight);
  690.         WELongRectToRect(&selRect, &r);
  691.         FrameRect(&r);
  692.     }
  693.     else
  694.     {
  695.         // selection range encompasses more than one line
  696.         // hilite the first line
  697.         WESetLongRect(&selRect, firstPoint.h, firstPoint.v, pWE->destRect.right, firstPoint.v + firstLineHeight);
  698.         WELongRectToRect(&selRect, &r);
  699.         FrameRect(&r);
  700.  
  701.         // any lines between the first and the last one?
  702.         if (firstPoint.v + firstLineHeight < lastPoint.v)
  703.         {
  704.             // hilite all the lines in-between
  705.             WESetLongRect(&selRect, pWE->destRect.left, firstPoint.v + firstLineHeight, pWE->destRect.right, lastPoint.v);
  706.             WELongRectToRect(&selRect, &r);
  707.             FrameRect(&r);
  708.         }
  709.  
  710.         // hilite the last line
  711.         WESetLongRect(&selRect, pWE->destRect.left, lastPoint.v, lastPoint.h, lastPoint.v + lastLineHeight);
  712.         WELongRectToRect(&selRect, &r);
  713.         FrameRect(&r);
  714.     }
  715.  
  716.     // copy the accumulated region into a new region
  717.     hiliteRgn = NewRgn();
  718.     CloseRgn(hiliteRgn);
  719.  
  720.     // restrict this region to the view region
  721.     SectRgn(hiliteRgn, pWE->viewRgn, hiliteRgn);
  722.  
  723.     // restore the port
  724.     SetPort(savePort);
  725.  
  726.     // unlock the WE record
  727.     _WESetHandleLock((Handle) hWE, saveWELock);
  728.  
  729.     // return the hilite region
  730.     return hiliteRgn;
  731. }
  732.  
  733. pascal void _WEHiliteRange(SInt32 rangeStart, SInt32 rangeEnd, WEHandle hWE)
  734. {
  735.     WEPtr pWE;
  736.     RgnHandle saveClip, auxRgn, hiliteRgn;
  737.     PenState savePen;
  738.     GrafPtr savePort;
  739.  
  740.     // the WE record must be already locked
  741.     pWE = *hWE;
  742.  
  743.     // do nothing if the specified range is empty
  744.     if (rangeStart == rangeEnd)
  745.     {
  746.         return;
  747.     }
  748.  
  749.     // set up the port
  750.     GetPort(&savePort);
  751.     SetPort(pWE->port);
  752.  
  753.     // create auxiliary regions
  754.     saveClip = NewRgn();
  755.     auxRgn = NewRgn();
  756.  
  757.     // restrict the clip region to the view rectangle
  758.     GetClip(saveClip);
  759.     SectRgn(saveClip, pWE->viewRgn, auxRgn);
  760.     SetClip(auxRgn);
  761.  
  762.     // get the hilite region corresponding to the specified range
  763.     hiliteRgn = WEGetHiliteRgn(rangeStart, rangeEnd, hWE);
  764.  
  765.     // hilite the region or frame it, depending on the setting of the active flag
  766.     if (BTST(pWE->flags, weFActive))
  767.     {
  768.         _WEClearHiliteBit();
  769.         InvertRgn(hiliteRgn);
  770.     }
  771.     else if (BTST(pWE->features, weFOutlineHilite))
  772.     {
  773.         GetPenState(&savePen);
  774.         PenNormal();
  775.         PenMode(patXor);
  776.         _WEClearHiliteBit();
  777.         FrameRgn(hiliteRgn);
  778.         SetPenState(&savePen);
  779.     }
  780.  
  781.     // restore the clip region
  782.     SetClip(saveClip);
  783.  
  784.     // dispose of all regions
  785.     DisposeRgn(saveClip);
  786.     DisposeRgn(auxRgn);
  787.     DisposeRgn(hiliteRgn);
  788.  
  789.     // restore the port
  790.     SetPort(savePort);
  791. }
  792.  
  793. pascal void WESetSelection(SInt32 selStart, SInt32 selEnd, WEHandle hWE)
  794. {
  795.     WEPtr pWE;
  796.     SInt32 oldSelStart, oldSelEnd;
  797.     Boolean saveWELock;
  798.  
  799.     // lock the WE record
  800.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  801.     pWE = *hWE;
  802.  
  803.     // range-check parameters
  804.     selStart = _WEPinInRange(selStart, 0, pWE->textLength);
  805.     selEnd = _WEPinInRange(selEnd, 0, pWE->textLength);
  806.  
  807.     // set the weFAnchorIsEnd bit if selStart > selEnd,  reorder the endpoints
  808.     if (selStart > selEnd)
  809.     {
  810.         BSET(pWE->flags, weFAnchorIsEnd);
  811.         _WEReorder(&selStart, &selEnd);
  812.     }
  813.     else
  814.     {
  815.         BCLR(pWE->flags, weFAnchorIsEnd);
  816.     }
  817.  
  818.     // get old selection range
  819.     oldSelStart = pWE->selStart;
  820.     oldSelEnd = pWE->selEnd;
  821.  
  822.     // selection changed?
  823.     if ((oldSelStart != selStart) || (oldSelEnd != selEnd))
  824.     {
  825.         // invalid the null style
  826.         BCLR(pWE->flags, weFUseNullStyle);
  827.  
  828.         // hide the caret if it's showing
  829.         if (BTST(pWE->flags, weFCaretVisible))
  830.         {
  831.             _WEBlinkCaret(hWE);
  832.         }
  833.  
  834.         // set new selection range
  835.         pWE->selStart = selStart;
  836.         pWE->selEnd = selEnd;
  837.  
  838.         // skip this section if either recalc or redraw has been inhibited
  839.         if (! (pWE->features & ((1L << weFInhibitRecal) | (1L << weFInhibitRedraw))))
  840.         {
  841.             // if we're active, invert the exclusive-OR between the old range and the new range.
  842.             // if we're inactive, this optimization can't be used because of outline highlighting.
  843.             if (BTST(pWE->flags, weFActive))
  844.             {
  845.                 _WEReorder(&oldSelStart, &selStart);
  846.                 _WEReorder(&oldSelEnd, &selEnd);
  847.                 _WEReorder(&oldSelEnd, &selStart);
  848.             }
  849.  
  850.             _WEHiliteRange(oldSelStart, oldSelEnd, hWE);
  851.             _WEHiliteRange(selStart, selEnd, hWE);
  852.  
  853.             if (!BTST(pWE->flags, weFMouseTracking))
  854.             {
  855.                 // redraw the caret immediately, if the selection range is empty
  856.                 if (pWE->selStart == pWE->selEnd)
  857.                 {
  858.                     _WEBlinkCaret(hWE);
  859.                 }
  860.  
  861.                 // clear clickCount, unless we're tracking the mouse
  862.                 pWE->clickCount = 0;
  863.  
  864.                 // scroll the selection into view, unless we're tracking the mouse
  865.                 WESelView(hWE);
  866.  
  867.             }
  868.         } // if redrawing not inhibited
  869.     } // if selection changed
  870.  
  871.     // unlock the WE record
  872.     _WESetHandleLock((Handle) hWE, saveWELock);
  873. }
  874.  
  875. pascal void WESetAlignment(WEAlignment alignment, WEHandle hWE)
  876. {
  877.     WEPtr pWE;
  878.     WEAlignment oldAlignment;
  879.     Boolean saveWELock;
  880.  
  881.     // lock the WE record
  882.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  883.     pWE = *hWE;
  884.     oldAlignment = pWE->alignment;
  885.  
  886.     if ((alignment >= weFlushLeft) && (alignment <= weJustify) && (alignment != oldAlignment))
  887.     {
  888.         // hide the caret if it's showing
  889.         if (BTST(pWE->flags, weFCaretVisible))
  890.             _WEBlinkCaret(hWE);
  891.  
  892.         // change the alignment
  893.         pWE->alignment = alignment;
  894.  
  895.         // if the text was left-aligned, then we haven't been bothering till now,
  896.         // so we have to recalc the whole document
  897.         if (oldAlignment == weFlushLeft)
  898.             _WERecalSlops(0, pWE->nLines - 1, hWE);
  899.  
  900.         // redraw the view rectangle
  901.         WEUpdate(nil, hWE);
  902.     }
  903.  
  904.     // unlock the WE record
  905.     _WESetHandleLock((Handle) hWE, saveWELock);
  906. }
  907.  
  908. pascal SInt32 _WEArrowOffset(SInt16 action, SInt32 offset, WEHandle hWE)
  909. {
  910.     // given an action code (corresponding to a modifiers + arrow key combo)
  911.     // and an offset into the text, find the offset of the new caret position
  912.  
  913.     LongPt thePoint;
  914.     SInt32 textLength, rangeStart, rangeEnd;
  915.     SInt16 lineHeight;
  916. #if WASTE_KURTHS_OPTION_ARROWS
  917.     SInt16 cType;
  918. #endif
  919.  
  920.     textLength = (*hWE)->textLength;
  921.     switch (action)
  922.     {
  923.         case kGoLeft:
  924.             if (offset > 0)
  925.             {
  926.                 offset--;
  927.                 if (WECharByte(offset, hWE) != smSingleByte)
  928.                 {
  929.                     offset--;
  930.                 }
  931.             }
  932.             break;
  933.  
  934.         case kGoRight:
  935.             if (offset < textLength)
  936.             {
  937.                 if (WECharByte(offset, hWE) != smSingleByte)
  938.                 {
  939.                     offset++;
  940.                 }
  941.                 offset++;
  942.             }
  943.             break;
  944.  
  945.         case kGoUp:
  946.             WEGetPoint(offset, hilite, &thePoint, nil, hWE);
  947.             thePoint.v--;
  948.             offset = WEGetOffset(&thePoint, nil, hWE);
  949.             break;
  950.  
  951.         case kGoDown:
  952.             WEGetPoint(offset, hilite, &thePoint, &lineHeight, hWE);
  953.             thePoint.v += lineHeight;
  954.             offset = WEGetOffset(&thePoint, nil, hWE);
  955.             break;
  956.  
  957.         case kGoWordStart:
  958. #if WASTE_KURTHS_OPTION_ARROWS
  959.             // loop "forever" (until we break out of it)
  960.             while (true)
  961.             {
  962.                 WEFindWord(offset, kTrailingEdge, &rangeStart, &rangeEnd, hWE);
  963.                 offset = rangeStart;
  964.  
  965.                 // If the found range is empty, get outta here.  (Most
  966.                 // likely this means that we have reached the beginning
  967.                 // of the text.)
  968.                 if (rangeStart == rangeEnd)
  969.                     break;
  970.  
  971.                 cType = WECharType(rangeStart, hWE);
  972.  
  973.                 // If the char is punctuation (other than a number),
  974.                 // it's not really a word, so keep looping.  Otherwise
  975.                 // we're done.
  976.                 if (((cType & smcTypeMask) != smCharPunct) ||
  977.                     ((cType & smcClassMask) == smPunctNumber))
  978.                 {
  979.                     break;
  980.                 }
  981.             }
  982. #else
  983.             WEFindWord(offset, kTrailingEdge, &rangeStart, &rangeEnd, hWE);
  984.             offset = rangeStart;
  985. #endif // WASTE_KURTHS_OPTION_ARROWS
  986.             break;
  987.  
  988.         case kGoWordEnd:
  989. #if WASTE_KURTHS_OPTION_ARROWS
  990.             // loop "forever" (until we break out of it)
  991.             while (true)
  992.             {
  993.                 WEFindWord(offset, kLeadingEdge, &rangeStart, &rangeEnd, hWE);
  994.                 offset = rangeEnd;
  995.  
  996.                 // If the found range is empty, get outta here.
  997.                 // (Most likely this means that we have reached
  998.                 // the end of the text.)
  999.                 if (rangeStart == rangeEnd)
  1000.                     break;
  1001.  
  1002.                 // `rangeEnd - 1' may point in the middle of a two-byte
  1003.                 // character; that's ok, CharType can deal with that.
  1004.                 cType = WECharType(rangeEnd - 1, hWE);
  1005.  
  1006.                 // If the char is punctuation (other than a number),
  1007.                 // it's not really a word, so keep looping.  Otherwise
  1008.                 // we're done.
  1009.                 if (((cType & smcTypeMask) != smCharPunct) ||
  1010.                     ((cType & smcClassMask) == smPunctNumber))
  1011.                 {
  1012.                     break;
  1013.                 }
  1014.             }
  1015. #else
  1016.             WEFindWord(offset, kLeadingEdge, &rangeStart, &rangeEnd, hWE);
  1017.             offset = rangeEnd;
  1018. #endif // WASTE_KURTHS_OPTION_ARROWS
  1019.             break;
  1020.  
  1021.         case kGoTextStart:
  1022.             offset = 0;
  1023.             break;
  1024.  
  1025.         case kGoTextEnd:
  1026.             offset = textLength;
  1027.             break;
  1028.  
  1029.         case kGoLineStart:
  1030.             WEFindLine(offset, kLeadingEdge, &rangeStart, &rangeEnd, hWE);
  1031.             offset = rangeStart;
  1032.             break;
  1033.  
  1034.         case kGoLineEnd:
  1035.             WEFindLine(offset, kTrailingEdge, &rangeStart, &rangeEnd, hWE);
  1036.             offset = rangeEnd;
  1037.             if (offset < textLength)
  1038.             {
  1039.                 offset--;
  1040.                 if (WECharByte(offset, hWE) != smSingleByte)
  1041.                 {
  1042.                     offset--;
  1043.                 }
  1044.             }
  1045.             break;
  1046.  
  1047.         default:
  1048.             break;
  1049.     }
  1050.  
  1051.     return offset;
  1052. }
  1053.  
  1054. pascal void _WEDoArrowKey (SInt16 arrow, EventModifiers modifiers, WEHandle hWE)
  1055. {
  1056.     // this routine is called by WEKey to handle arrow keys
  1057.  
  1058.     WEPtr pWE = *hWE;    // assume the WE record is already locked
  1059.     SInt16 action;
  1060.     SInt32 selStart, selEnd;
  1061.     SInt32 caretLoc, anchor;
  1062.  
  1063.     // calculate the "action" parameter for _WEArrowOffset from arrow and modifiers
  1064.     action = arrow - kArrowLeft;            // possible range: 0..3
  1065.     if (modifiers & optionKey)
  1066.     {
  1067.         action += kOption;
  1068.     }
  1069.     if (modifiers & cmdKey)
  1070.     {
  1071.         action += kCommand;
  1072.     }
  1073.  
  1074.     // get selection range
  1075.     selStart = pWE->selStart;
  1076.     selEnd = pWE->selEnd;
  1077.  
  1078.     if ((modifiers & shiftKey) == 0)
  1079.     {
  1080.         // if selection range isn't empty, collapse it to one of the endpoints
  1081.         if (selStart < selEnd)
  1082.         {
  1083.             if ((arrow == kArrowLeft) || (arrow == kArrowUp))
  1084.             {
  1085.                 caretLoc = selStart;
  1086.             }
  1087.             else
  1088.             {
  1089.                 caretLoc = selEnd;
  1090.             }
  1091.         }
  1092.         else
  1093.         {
  1094.             // otherwise move the insertion point
  1095.             caretLoc = _WEArrowOffset(action, selStart, hWE);
  1096.         }
  1097.  
  1098.         // set anchor to caretLoc, so new selection will be empty
  1099.         anchor = caretLoc;
  1100.     }
  1101.     else
  1102.     {
  1103.         // shift key was held down: extend the selection rather than replacing it
  1104.         // find out which selection boundary is the anchor and which is the free endpoint
  1105.         if (BTST(pWE->flags, weFAnchorIsEnd))
  1106.         {
  1107.             anchor = selEnd;
  1108.             caretLoc = selStart;
  1109.         }
  1110.         else
  1111.         {
  1112.             anchor = selStart;
  1113.             caretLoc = selEnd;
  1114.         }
  1115.  
  1116.         // move the free endpoint
  1117.         caretLoc = _WEArrowOffset(action, caretLoc, hWE);
  1118.     }
  1119.  
  1120.     // select the new selection
  1121.     WESetSelection(anchor, caretLoc, hWE);
  1122. }
  1123.  
  1124. pascal Boolean WEAdjustCursor(Point mouseLoc, RgnHandle mouseRgn, WEHandle hWE)
  1125. {
  1126.     // Call WEAdjustCursor to set the cursor shape when the mouse is in the view rectangle.
  1127.     // MouseRgn should be either a valid region handle or nil.
  1128.     // If mouseRgn is supplied (i.e., if it's not nil), it is intersected with a region
  1129.     // in global coordinates within which the cursor is to retain its shape.
  1130.     // WEAdjustCursor returns true if the cursor has been set.
  1131.     // Your application should set the cursor only if WEAdjustCursor returns false.
  1132.  
  1133.     WEPtr pWE;
  1134.     RgnHandle auxRgn, hiliteRgn;
  1135.     enum { kIBeam, kArrow} cursorType;
  1136.     Point portDelta;
  1137.     GrafPtr savePort;
  1138.     Boolean saveWELock;
  1139.     Boolean adjustCursor;
  1140.  
  1141.     adjustCursor = false;
  1142.     cursorType = kIBeam;
  1143.  
  1144.     // lock the WE record
  1145.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1146.     pWE = *hWE;
  1147.  
  1148.     // set up the port
  1149.     GetPort(&savePort);
  1150.     SetPort(pWE->port);
  1151.  
  1152.     // calculate delta between the local coordinate system and the global one
  1153.     portDelta.v = 0;
  1154.     portDelta.h = 0;
  1155.     LocalToGlobal(&portDelta);
  1156.  
  1157.     // calculate the visible portion of the view rectangle, in global coordinates
  1158.     auxRgn = NewRgn();
  1159.     CopyRgn(pWE->viewRgn, auxRgn);
  1160.     SectRgn(auxRgn, pWE->port->visRgn, auxRgn);
  1161.     OffsetRgn(auxRgn, portDelta.h, portDelta.v);
  1162.  
  1163.     if (PtInRgn(mouseLoc, auxRgn))
  1164.     {
  1165.         // mouse is within view rectangle: it's up to us to set the cursor
  1166.         adjustCursor = true;
  1167.  
  1168.         // if drag-and-drop is enabled, see if the mouse is within current selection
  1169.         if (BTST(pWE->flags, weFHasDragManager) && BTST(pWE->features, weFDragAndDrop))
  1170.         {
  1171.             if (pWE->selStart < pWE->selEnd)
  1172.             {
  1173.  
  1174.                 // get current hilite region in global coordinates
  1175.                 hiliteRgn = WEGetHiliteRgn(pWE->selStart, pWE->selEnd, hWE);
  1176.                 OffsetRgn(hiliteRgn, portDelta.h, portDelta.v);
  1177.  
  1178.                 // if mouse is within selection, set cursor to an arrow, else to an I-beam
  1179.                 // (actually, we still use an I-beam if less than GetDoubleClickTime() ticks have
  1180.                 // elapsed since the last mouse click, so that the cursor doesn't turn into an
  1181.                 // arrow while triple-clicking + dragging a range of lines)
  1182.  
  1183.                 if (PtInRgn(mouseLoc, hiliteRgn) && ((TickCount() > pWE->clickTime + GetDoubleClickTime()) ||
  1184.                     (pWE->clickEdge == kObjectEdge)))
  1185.                 {
  1186.                     cursorType = kArrow;                // use arrow cursor
  1187.                     CopyRgn(hiliteRgn, auxRgn);
  1188.                 }
  1189.                 else
  1190.                 {
  1191.                     DiffRgn(auxRgn, hiliteRgn, auxRgn);
  1192.                 }
  1193.  
  1194.                 // dispose of the hilite region
  1195.                 DisposeRgn(hiliteRgn);
  1196.  
  1197.             } // if drag-and-drop is enabled
  1198.         }
  1199.  
  1200.         // set the cursor
  1201.         if (cursorType == kIBeam)
  1202.             SetCursor(*GetCursor(iBeamCursor));
  1203.         else
  1204.             SetCursor(&qd.arrow);
  1205.  
  1206.         // set mouseRgn, if provided
  1207.         if (mouseRgn != nil)
  1208.         {
  1209.             SectRgn(mouseRgn, auxRgn, mouseRgn);
  1210.         }
  1211.     }
  1212.     else
  1213.     {
  1214.         // mouse is outside view rectangle: don't set the cursor; subtract viewRgn from mouseRgn
  1215.         if (mouseRgn != nil)
  1216.         {
  1217.             DiffRgn(mouseRgn, auxRgn, mouseRgn);
  1218.         }
  1219.     }
  1220.     // dispose of the temporary region
  1221.     DisposeRgn(auxRgn);
  1222.  
  1223.     // restore the port
  1224.     SetPort(savePort);
  1225.  
  1226.     // unlock the WE record
  1227.     _WESetHandleLock((Handle) hWE, saveWELock);
  1228.  
  1229.     return adjustCursor;
  1230. }
  1231.  
  1232. pascal void WEIdle(UInt32 *maxSleep, WEHandle hWE)
  1233. {
  1234.     WEPtr pWE;
  1235.     UInt32 currentTime, blinkTime, sleep;
  1236.     Boolean saveWELock;
  1237.  
  1238.     // lock the WE record
  1239.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1240.     pWE = *hWE;
  1241.  
  1242. #if WASTE_DEBUG
  1243.         _WESanityCheck(hWE);
  1244. #endif
  1245.  
  1246.     // the caret blinks only if we're active and the selection point is empty
  1247.     if (BTST(pWE->flags, weFActive) && (pWE->selStart == pWE->selEnd))
  1248.     {
  1249.         // get current time
  1250.         currentTime = TickCount();
  1251.  
  1252.         // calculate when the caret should be blinked again
  1253.         blinkTime = pWE->caretTime + GetCaretTime();
  1254.  
  1255.         if (currentTime < blinkTime)
  1256.         {
  1257.             sleep = blinkTime - currentTime;
  1258.         }
  1259.         else
  1260.         {
  1261.             _WEBlinkCaret(hWE);
  1262.             sleep = GetCaretTime();
  1263.         }
  1264.     }
  1265.     else
  1266.     {
  1267.         // if we don't need to blink the caret, we can sleep forever
  1268.         sleep = LONG_MAX;
  1269.     }
  1270.  
  1271.     // return sleepTime to the caller if maxSleep isn't nil
  1272.     if (maxSleep != nil)
  1273.     {
  1274.         *maxSleep = sleep;
  1275.     }
  1276.  
  1277.     // unlock the WE record
  1278.     _WESetHandleLock((Handle) hWE, saveWELock);
  1279. }
  1280.  
  1281. pascal void WEUpdate(RgnHandle updateRgn, WEHandle hWE)
  1282. {
  1283.     WEPtr pWE;
  1284.     LongRect updateRect;
  1285.     Rect r;
  1286.     RgnHandle saveClip, auxRgn;
  1287.     GrafPtr savePort;
  1288.     Boolean saveWELock;
  1289.  
  1290.     // lock the WE record
  1291.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1292.     pWE = *hWE;
  1293.  
  1294.     // set up the port
  1295.     GetPort(&savePort);
  1296.     SetPort(pWE->port);
  1297.  
  1298.     // save the clip region
  1299.     saveClip = NewRgn();
  1300.     GetClip(saveClip);
  1301.  
  1302.     // clip to the insersection between updateRgn and the view rectangle
  1303.     // (updateRgn may be nil; in this case, just clip to the view rectangle)
  1304.     auxRgn = NewRgn();
  1305.     if (updateRgn != nil)
  1306.     {
  1307.         SectRgn(updateRgn, pWE->viewRgn, auxRgn);
  1308.     }
  1309.     else
  1310.     {
  1311.         CopyRgn(pWE->viewRgn, auxRgn);
  1312.     }
  1313.     SetClip(auxRgn);
  1314.  
  1315.     if (!EmptyRgn(auxRgn))
  1316.     {
  1317.         // calculate the rectangle to update
  1318.         r = (*auxRgn)->rgnBBox;
  1319.         WERectToLongRect(&r, &updateRect);
  1320.  
  1321.         // find out which lines need to be redrawn and draw them
  1322.         // if updateRgn is nil, erase each line rectangle before redrawing
  1323.         _WEDrawLines( _WEPixelToLine(updateRect.top - pWE->destRect.top, hWE),
  1324.                       _WEPixelToLine((updateRect.bottom - 1) - pWE->destRect.top, hWE),
  1325.                       (updateRgn == nil), hWE);
  1326.  
  1327.         // erase the portion of the update rectangle below the last line (if any)
  1328.         updateRect.top = pWE->destRect.top + (*pWE->hLines)[pWE->nLines].lineOrigin;
  1329.         if (updateRect.top < updateRect.bottom)
  1330.         {
  1331.             WELongRectToRect(&updateRect, &r);
  1332.             EraseRect(&r);
  1333.         }
  1334.  
  1335.         // hilite the selection range or draw the caret (only if active)
  1336.         if (pWE->selStart < pWE->selEnd)
  1337.         {
  1338.             _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  1339.         }
  1340.         else if (BTST(pWE->flags, weFCaretVisible))
  1341.         {
  1342.             _WEBlinkCaret(hWE);
  1343.             BSET(pWE->flags, weFCaretVisible);
  1344.         }
  1345.     }
  1346.  
  1347.     DisposeRgn(auxRgn);
  1348.  
  1349.     // restore the clip region
  1350.     SetClip(saveClip);
  1351.     DisposeRgn(saveClip);
  1352.  
  1353.     // restore the port
  1354.     SetPort(savePort);
  1355.  
  1356.     // unlock the WE record
  1357.     _WESetHandleLock((Handle) hWE, saveWELock);
  1358. }
  1359.  
  1360. pascal void WEDeactivate(WEHandle hWE)
  1361. {
  1362.     WEPtr pWE;
  1363.     Boolean saveWELock;
  1364.  
  1365.     // lock the WE record
  1366.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1367.     pWE = *hWE;
  1368.  
  1369.     // do nothing if we are already inactive
  1370.     if (BTST(pWE->flags, weFActive))
  1371.     {
  1372.  
  1373.         // hide the selection range or the caret
  1374.         _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  1375.         if (BTST(pWE->flags, weFCaretVisible))
  1376.             _WEBlinkCaret(hWE);
  1377.  
  1378.         // clear the active flag
  1379.         BCLR(pWE->flags, weFActive);
  1380.  
  1381.         // frame the selection
  1382.         _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  1383.  
  1384.         // dispose of the offscreen graphics world, if any
  1385.         if (pWE->offscreenPort != nil)
  1386.         {
  1387.             DisposeGWorld((GWorldPtr)(pWE->offscreenPort));
  1388.             pWE->offscreenPort = nil;
  1389.         }
  1390.  
  1391.         // notify Text Services
  1392.         if (pWE->tsmReference != nil)
  1393.             DeactivateTSMDocument(pWE->tsmReference);
  1394.     }
  1395.  
  1396.     // unlock the WE record
  1397.     _WESetHandleLock((Handle) hWE, saveWELock);
  1398. }
  1399.  
  1400. pascal void WEActivate(WEHandle hWE)
  1401. {
  1402.     WEPtr pWE;
  1403.     Boolean saveWELock;
  1404.  
  1405.     if (WEIsActive(hWE)) return;
  1406.  
  1407.     // lock the WE record
  1408.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1409.     pWE = *hWE;
  1410.  
  1411.     // remove the selection frame
  1412.     _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  1413.  
  1414.     // set the active flag
  1415.     BSET(pWE->flags, weFActive);
  1416.  
  1417.     // show the selection range
  1418.     _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  1419.  
  1420.     // notify Text Services
  1421.     if (pWE->tsmReference != nil)
  1422.     {
  1423.         ActivateTSMDocument(pWE->tsmReference);
  1424.     }
  1425.  
  1426.     // unlock the WE record
  1427.     _WESetHandleLock((Handle) hWE, saveWELock);
  1428. }
  1429.  
  1430. pascal Boolean WEIsActive(WEHandle hWE)
  1431. {
  1432.     // return true iff the specified WE instance is currently active
  1433.     return BTST((*hWE)->flags, weFActive) ? true : false;
  1434. }
  1435.  
  1436. pascal void WEScroll(SInt32 hOffset, SInt32 vOffset, WEHandle hWE)
  1437. {
  1438.     WEPtr pWE;
  1439.     Rect viewRect;
  1440.     GrafPtr savePort;
  1441.     Boolean hideOutline, saveWELock;
  1442.  
  1443.     // do nothing if both scroll offsets are zero
  1444.     if ((hOffset == 0) && (vOffset == 0))
  1445.         return;
  1446.  
  1447.     // lock the WE record
  1448.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1449.     pWE = *hWE;
  1450.  
  1451.     // set up the port
  1452.     GetPort(&savePort);
  1453.     SetPort(pWE->port);
  1454.  
  1455.     // get view rect in short coordinates
  1456.     viewRect = (*pWE->viewRgn)->rgnBBox;
  1457.  
  1458.     // hide the caret if it's showing
  1459.     if (BTST(pWE->flags, weFCaretVisible))
  1460.     {
  1461.         _WEBlinkCaret(hWE);
  1462.     }
  1463.  
  1464.     // if we're inactive and outline highlighting is on, we have to temporarily
  1465.     // hide the selection outline while scrolling to avoid a cosmetic bug
  1466.     hideOutline = false;
  1467.     if (!BTST(pWE->flags, weFActive))
  1468.         if (BTST(pWE->features, weFOutlineHilite))
  1469.         {
  1470.             hideOutline = true;
  1471.             _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  1472.             BCLR(pWE->features, weFOutlineHilite);
  1473.         }
  1474.  
  1475.     // offset the destination rectangle by the specified amount
  1476.     WEOffsetLongRect(&pWE->destRect, hOffset, vOffset);
  1477.  
  1478.     // scroll the view rectangle
  1479.     // we use ScrollRect unless the whole text is to be redrawn anyway
  1480.     // notice that both ScrollRect and DragPreScroll take short (16-bit)
  1481.     // offset parameters, while WEScroll deals with long (32-bit) quantities
  1482.     if ((ABS(hOffset) < (viewRect.right - viewRect.left)) && (ABS(vOffset) < (viewRect.bottom - viewRect.top)))
  1483.     {
  1484.         RgnHandle updateRgn = NewRgn();
  1485.  
  1486.         // if we're currently tracking a drag, notify the Drag Manager we're about to scroll
  1487.         if (pWE->currentDrag != kNullDrag)
  1488.             DragPreScroll(pWE->currentDrag, (SInt16) hOffset, (SInt16) vOffset);
  1489.  
  1490.         // ScrollRect will set updateRgn to the region to redraw
  1491.         ScrollRect(&viewRect, (SInt16) hOffset, (SInt16) vOffset, updateRgn);
  1492.  
  1493.         if (pWE->currentDrag != kNullDrag)
  1494.             DragPostScroll(pWE->currentDrag);
  1495.  
  1496.         // redraw the exposed region
  1497.         WEUpdate(updateRgn, hWE);
  1498.         DisposeRgn(updateRgn);
  1499.     }
  1500.     else
  1501.     {
  1502.         // redraw the whole text
  1503.         WEUpdate(nil, hWE);
  1504.     }
  1505.  
  1506.     // redraw the selection outline, if hidden
  1507.     if (hideOutline)
  1508.     {
  1509.         BSET(pWE->features, weFOutlineHilite);
  1510.         _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  1511.     }
  1512.  
  1513.     // restore the port
  1514.     SetPort(savePort);
  1515.  
  1516.     // unlock the WE record
  1517.     _WESetHandleLock((Handle) hWE, saveWELock);
  1518. }
  1519.  
  1520. pascal void WEPinScroll(SInt32 hOffset, SInt32 vOffset, WEHandle hWE)
  1521. {
  1522.     WEPtr pWE = *hWE;    // we ain't gonna move memory
  1523.     SInt32 delta;
  1524.  
  1525.     if (vOffset > 0)
  1526.     {
  1527.         delta = pWE->viewRect.top - pWE->destRect.top;
  1528.  
  1529.         // if top of the destRect would be moved below top of the viewRect,
  1530.         // pin it to top of the viewRect
  1531.         if (vOffset > delta)
  1532.         {
  1533.             vOffset = delta;
  1534.         }
  1535.     }
  1536.     else if (vOffset < 0)
  1537.     {
  1538.         delta = pWE->viewRect.bottom - pWE->destRect.bottom;
  1539.  
  1540.         // if bottom of the destRect would be moved above bottom of the viewRect,
  1541.         // pin it to bottom of viewRect
  1542.         if (vOffset < delta)
  1543.         {
  1544.             vOffset = delta;
  1545.         }
  1546.     }
  1547.  
  1548.     WEScroll(hOffset, vOffset, hWE);
  1549. }
  1550.  
  1551. pascal Boolean _WEScrollIntoView (SInt32 offset, WEHandle hWE)
  1552. {
  1553.     WEPtr pWE = *hWE;
  1554.     LongPt thePoint;
  1555.     SInt16 lineHeight;
  1556.     SInt32 hScroll, vScroll, temp;
  1557.     Boolean retval;
  1558.  
  1559.     // do nothing if automatic scrolling is disabled
  1560.     if (!BTST(pWE->features, weFAutoScroll))
  1561.     {
  1562.         return false;
  1563.     }
  1564.  
  1565.     // find the selection point
  1566.     WEGetPoint(offset, hilite, &thePoint, &lineHeight, hWE);
  1567.  
  1568.     // assume no scrolling is needed
  1569.     retval = false;
  1570.     vScroll = 0;
  1571.     hScroll = 0;
  1572.  
  1573.     // determine if we need to scroll vertically
  1574.     if ((thePoint.v < pWE->viewRect.top) ||
  1575.         (thePoint.v + lineHeight >= pWE->viewRect.bottom))
  1576.     {
  1577.         // calculate the amount of vertical scrolling needed to center the selection into view
  1578.         vScroll = ((pWE->viewRect.top + pWE->viewRect.bottom) >> 1) -
  1579.                     (thePoint.v + (lineHeight >> 1));
  1580.  
  1581.         // we'd like to superimpose the bottom margins of the dest/view rects, if possible
  1582.         temp = pWE->viewRect.bottom - pWE->destRect.bottom;
  1583.         if (temp > vScroll)
  1584.         {
  1585.             vScroll = temp;
  1586.         }
  1587.         // but we also have to make sure the dest top isn't scrolled below the view top
  1588.         temp = pWE->viewRect.top - pWE->destRect.top;
  1589.         if (temp < vScroll)
  1590.         {
  1591.             vScroll = temp;
  1592.         }
  1593.     }
  1594.  
  1595.     // determine if we need to scroll horizontally
  1596.     if ((thePoint.h - 1 < pWE->viewRect.left) || (thePoint.h >= pWE->viewRect.right))
  1597.     {
  1598.         // calculate the amount of horizontal scrolling needed to center the selection into view
  1599.         hScroll = ((pWE->viewRect.left + pWE->viewRect.right) >> 1) - thePoint.h;
  1600.  
  1601.         // we'd like to superimpose the right margins of the dest/view rects, if possible
  1602.         temp = pWE->viewRect.right - pWE->destRect.right;
  1603.         if (temp > hScroll)
  1604.         {
  1605.             hScroll = temp;
  1606.         }
  1607.  
  1608.         // but we also have to make sure the dest left isn't scrolled to the right of the view left
  1609.         temp = pWE->viewRect.left - pWE->destRect.left;
  1610.         if (temp < hScroll)
  1611.         {
  1612.             hScroll = temp;
  1613.         }
  1614.     }
  1615.  
  1616.     // scroll the text if necessary
  1617.     if ((vScroll != 0) || (hScroll != 0))
  1618.     {
  1619.         retval = true;
  1620.         WEScroll(hScroll, vScroll, hWE);
  1621.         BSET(pWE->flags, weFDestRectChanged);
  1622.     }
  1623.  
  1624.     // notify our client of changes to the destination rectangle
  1625.     if (BTST(pWE->flags, weFDestRectChanged))
  1626.     {
  1627.         if (pWE->scrollProc != nil)
  1628.         {
  1629.             CallWEScrollProc(hWE, pWE->scrollProc);
  1630.         }
  1631.         BCLR(pWE->flags, weFDestRectChanged);
  1632.     }
  1633.  
  1634.     // call the scroll callback, if any
  1635.     return retval;
  1636. }
  1637.  
  1638. pascal void WESelView(WEHandle hWE)
  1639. {
  1640.     WEPtr pWE;
  1641.     Boolean saveWELock;
  1642.  
  1643.     // lock the WE record
  1644.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1645.     pWE = *hWE;
  1646.  
  1647.     // scroll the free endpoint of the selection into view
  1648.     _WEScrollIntoView(BTST(pWE->flags, weFAnchorIsEnd) ? pWE->selStart : pWE->selEnd, hWE);
  1649.  
  1650.     // unlock the WE record
  1651.     _WESetHandleLock((Handle) hWE, saveWELock);
  1652. }
  1653.